#include "xnserial.h"

#ifdef XNLINUX
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#endif

#ifdef XNWIN
#include <stdio.h>
#endif

CXnSerial::CXnSerial()
{
#ifdef XNLINUX
	m_fdSerial = -1;
#endif

#ifdef XNWIN
	m_hdlSerial = INVALID_HANDLE_VALUE;
#endif
}

CXnSerial::~CXnSerial()
{
	closeDevice();
}

// Linux
// -1 - serial has open
// -2 - open serial FAILED
// -3 - get comm config FAILED
// -4 - set comm config FAILED

// window
// -1 - pDeviceName is NULL
// -2 - pDeviceName not correct
// -3 - open serial FAILED
// -4 - set comm config FAILED
int CXnSerial::openDevice(const char *pDeviceName, int iSpeed, int iBlock, int iRtscts, int iBits, int iStopBits, int iParity)
{
	// 参数 param 保存
	m_iSpeed = iSpeed;
	m_iBlock = iBlock;
	m_iRtscts = iRtscts;
	m_iBits = iBits;
	m_iStopBits = iStopBits;
	m_iParity = iParity;

#ifdef XNLINUX
	struct termios buf;

	if (m_fdSerial != -1)
	{
		return -1;
	}

	m_fdSerial = open(pDeviceName, O_RDWR | O_NOCTTY | O_NDELAY);
	if (m_fdSerial < 0) 
	{
		printf("CXnSerial::openDevice - open(%s) FAILED!\n", pDeviceName);
		return -2;
	}

	if (tcgetattr(m_fdSerial, &m_resetCommCfg) < 0)
	{ 
		return -3; 
	}

	buf = m_resetCommCfg;
	buf.c_cflag = iSpeed | CLOCAL | CREAD;
	if (iRtscts == 1)  buf.c_cflag |= CRTSCTS;
	if (iBits   == 7) 
	{
		buf.c_cflag |= CS7;
	}
	else             
	{
		buf.c_cflag |= CS8;
	}

	if (iStopBits == 2)  
	{
		buf.c_cflag |= CSTOPB;
	}

	if (iParity == CXnSerial::ODD)  buf.c_cflag |= (PARENB | PARODD);
	if (iParity == CXnSerial::EVEN) buf.c_cflag |= PARENB;

	buf.c_lflag = IEXTEN; //ICANON;
	buf.c_oflag     = OPOST;
	buf.c_cc[VMIN]  = 1;
	buf.c_cc[VTIME] = 0;
	buf.c_line      = 0;
	buf.c_iflag     = IGNBRK | IGNPAR | IXANY;
	if (tcsetattr(m_fdSerial, TCSAFLUSH, &buf) < 0)
	{
		return -4;
	}

	//if (tcsetattr(m_fdSerial, TCSANOW, &buf) < 0) { return -1; }

	if (iBlock == 1) 
	{
		fcntl(m_fdSerial, F_SETFL, fcntl(m_fdSerial, F_GETFL, 0) & ~O_NONBLOCK);
	}
	tcflush(m_fdSerial, TCIOFLUSH);

#endif

#ifdef XNWIN
	if (0 == pDeviceName) // name NULL
	{
		return -1;
	}

	if (strlen(pDeviceName) > 80) 
	{
		return -2;
	}

	// define some variables
	DWORD ccsize;
	COMMCONFIG cc;
	int baudrate;
	char devname[100];
	
	sprintf(devname,"\\\\.\\%s",pDeviceName);        // Aenderung: allow more than 4 COM ports
	m_hdlSerial = CreateFileA(
		devname,                       // devicename, // pointer to name of the file
		GENERIC_READ | GENERIC_WRITE,  // access (read-write) mode
		0,                             // share mode
		0,                             // pointer to security attributes
		OPEN_EXISTING,                 // how to create
		0,                             // not overlapped I/O
		0                              // handle to file with attributes to copy
		);
	if (m_hdlSerial == INVALID_HANDLE_VALUE)
	{
		printf("CXnSerial::openDevice - CreateFile(%s) FAILED!\n",pDeviceName);
		return -3;
	}

	baudrate = CBR_9600;
	if (iSpeed == B50     ) baudrate = 50;
	else if (iSpeed == B75     ) baudrate = 75;
	else if (iSpeed == B110    ) baudrate = CBR_110;
	else if (iSpeed == B134    ) baudrate = 134;
	else if (iSpeed == B150    ) baudrate = 150;
	else if (iSpeed == B200    ) baudrate = 200;
	else if (iSpeed == B300    ) baudrate = CBR_300;
	else if (iSpeed == B600    ) baudrate = CBR_600;
	else if (iSpeed == B1200   ) baudrate = CBR_1200;
	else if (iSpeed == B1800   ) baudrate = 1800;
	else if (iSpeed == B2400   ) baudrate = CBR_2400;
	else if (iSpeed == B4800   ) baudrate = CBR_4800;
	else if (iSpeed == B9600   ) baudrate = CBR_9600;
	else if (iSpeed == B19200  ) baudrate = CBR_19200;
	else if (iSpeed == B38400  ) baudrate = CBR_38400;
	else if (iSpeed == B57600  ) baudrate = CBR_57600;
	else if (iSpeed == B115200 ) baudrate = CBR_115200;
	else if (iSpeed == B230400 ) baudrate = 230400;
	else if (iSpeed == B460800 ) baudrate = 460800;
	else if (iSpeed == B500000 ) baudrate = 500000;
	else if (iSpeed == B576000 ) baudrate = 576000;
	else if (iSpeed == B921600 ) baudrate = 921600;
	else if (iSpeed == B1000000) baudrate = 1000000;
	else if (iSpeed == B1152000) baudrate = 1152000;
	else if (iSpeed == B1500000) baudrate = 1500000;
	else if (iSpeed == B2000000) baudrate = 2000000;
	else if (iSpeed == B2500000) baudrate = 2500000;
	else if (iSpeed == B3000000) baudrate = 3000000;
	else if (iSpeed == B3500000) baudrate = 3500000;
	else if (iSpeed == B4000000) baudrate = 4000000;

	ccsize = sizeof(cc);
	GetCommConfig(m_hdlSerial,&cc,&ccsize);
	//cc.dwSize            = sizeof(cc);  // size of structure
	//cc.wVersion          = 1;           // version of structure
	//cc.wReserved         = 0;           // reserved
	//  DCB   dcb;                      // device-control block
	cc.dcb.DCBlength     = sizeof(DCB); // sizeof(DCB)
	cc.dcb.BaudRate      = baudrate;    // current baud rate
	cc.dcb.fBinary       = 1;           // binary mode, no EOF check
	cc.dcb.fParity       = 1;           // enable parity checking
	cc.dcb.fOutxCtsFlow  = 0;           // CTS output flow control
	if (iRtscts == 1) cc.dcb.fOutxCtsFlow = 1;
	cc.dcb.fOutxDsrFlow  = 0;           // DSR output flow control
	cc.dcb.fDtrControl   = DTR_CONTROL_DISABLE;  // DTR flow control type
	cc.dcb.fDsrSensitivity   = 0;       // DSR sensitivity
	cc.dcb.fTXContinueOnXoff = 1;       // XOFF continues Tx
	//cc.dcb.fOutX         = 0;           // XON/XOFF out flow control
	//cc.dcb.fInX          = 0;           // XON/XOFF in flow control
	//cc.dcb.fErrorChar    = 0;           // enable error replacement
	cc.dcb.fNull         = 0;           // enable null stripping
	cc.dcb.fRtsControl   = RTS_CONTROL_DISABLE;
	if (iRtscts == 1)  cc.dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;  // RTS flow control
	cc.dcb.fAbortOnError = 0;           // abort reads/writes on error
	//cc.dcb.fDummy2       = 0;           // reserved
	//cc.dcb.wReserved     = 0;           // not currently used
	//cc.dcb.XonLim        = 0;           // transmit XON threshold
	//cc.dcb.XoffLim       = 0;           // transmit XOFF threshold
	cc.dcb.ByteSize      = iBits;        // number of bits/byte, 4-8
	cc.dcb.Parity        = 0;           // 0-4=no,odd,even,mark,space
	if (iParity == CXnSerial::ODD)    cc.dcb.Parity = 1;
	if (iParity == CXnSerial::EVEN)   cc.dcb.Parity = 2;
	cc.dcb.StopBits      = ONESTOPBIT;  // 0,1,2 = 1, 1.5, 2
	if (iStopBits == 2) cc.dcb.StopBits = TWOSTOPBITS;
	//cc.dcb.XonChar       = 0;           // Tx and Rx XON character
	//cc.dcb.XoffChar      = 0;           // Tx and Rx XOFF character
	//cc.dcb.ErrorChar     = 0;           // error replacement character
	//cc.dcb.EofChar       = 0;           // end of input character
	//cc.dcb.EvtChar       = 0;           // received event character
	//cc.dcb.wReserved1    = 0;           // reserved; do not use
	cc.dwProviderSubType = PST_RS232;   // type of provider-specific data
	//cc.dwProviderOffset  = 0;           // offset of provider-specific data
	//cc.dwProviderSize    = 0;           // size of provider-specific data
	//cc.wcProviderData[0] = 0;           // provider-specific data

	if (SetCommConfig(m_hdlSerial ,&cc, sizeof(cc)) == 0)
	{
		printf("CXnSerial::openDevice - SetCommConfig FAILED!(Device name=%s LastError=%ld)\n", pDeviceName, GetLastError());
		return -4;
	}
#endif

	return 0;
}

// -10 - pDeviceName is NULL
int CXnSerial::openDeviceAgain(const char *pDeviceName)
{
	if (0 == pDeviceName) // name NULL
	{
		return -10;
	}

	return openDevice(pDeviceName, m_iSpeed, m_iBlock, m_iRtscts, m_iBits, m_iStopBits, m_iParity);
}

// Linux
// -1 - serial not open
// -2 - set serial config FAILED
int CXnSerial::closeDevice()
{
#ifdef XNLINUX
	if (m_fdSerial == -1) 
	{
		return -1;
	}

	//if (::tcsetattr(m_fdSerial,TCSAFLUSH,&save_termios) < 0) return -1;

	if (::tcsetattr(m_fdSerial, TCSANOW, &m_resetCommCfg) < 0) 
	{
		return -2;
	}

	::close(m_fdSerial);
	m_fdSerial = -1;
#endif

#ifdef XNWIN
	CloseHandle(m_hdlSerial);
	m_hdlSerial = INVALID_HANDLE_VALUE;
#endif

	return 0;
}

int CXnSerial::select(int iTimeout)
{
#ifdef XNLINUX
	struct timeval timout;
	fd_set wset,rset,eset;
	int    ret,maxfdp1;

	if (iTimeout <= 0) return 1;
	/* setup sockets to read */
	maxfdp1 = m_fdSerial + 1;
	FD_ZERO(&rset);
	FD_SET (m_fdSerial, &rset);
	FD_ZERO(&wset);
	FD_ZERO(&eset);
	timout.tv_sec  =  iTimeout / 1000;
	timout.tv_usec = (iTimeout % 1000) * 1000;

	ret = ::select(maxfdp1, &rset ,&wset, &eset, &timout);
	if (ret == 0) 
	{
		return 0; /* timeout */
	}
#endif

#ifdef XNWIN
	COMMTIMEOUTS ctimeout;

	//ctimeout.ReadIntervalTimeout          = iTimeout;  // Modify by CGL 2016-10-31 
	ctimeout.ReadIntervalTimeout          = 30;          // 设置间隔超时时间为30ms
	ctimeout.ReadTotalTimeoutMultiplier   = 1; 
	ctimeout.ReadTotalTimeoutConstant     = iTimeout; 
	ctimeout.WriteTotalTimeoutMultiplier  = 1; 
	ctimeout.WriteTotalTimeoutConstant    = iTimeout; 

	SetCommTimeouts(m_hdlSerial, &ctimeout);
#endif

	return 1;
}

// Linux
// -1 - Serial not opened
// -2 - read bytes is NULL
// -3 - read FAILED

// Windows
// -1 - read serial FAILED
int CXnSerial::readChar()
{
#ifdef XNLINUX
	int  ret;
	unsigned char buf[2];

	if (m_fdSerial == -1) 
	{
		return -1;
	}

	ret = read(m_fdSerial, buf, 1);
	if (ret == 1)
	{
		return buf[0];
	}

	if (ret == 0) 
	{
		return -2;
	}

	return -3;
#endif

#ifdef XNWIN
	unsigned char buf[2];
	unsigned long len;

	ReadFile(
		m_hdlSerial,  // handle of file to read
		buf,  // pointer to buffer that receives data
		1,    // number of bytes to read
		&len, // pointer to number of bytes read
		NULL  // pointer to structure for data
		);
	if (len > 0)
	{
		return buf[0];
	}
	return -1;
#endif
}

// Linux 
// -1 - serial is not opened
// -2 - pBuf is NULL
// -3 - iLen < 0
// -4 - timeout
// -5 - read FAILED

// windows
// -1 - serial is not opened
// -2 - pBuf is NULL
// -3 - iLen < 0
// -4 - read FAILED

int CXnSerial::readBlock(unsigned char *pBuf, int iLen, int iTimeout)
{
	
#ifdef XNLINUX
 /* int c, retlen;

  retlen = 0;
  for (int i=0; i<len; i++)
  {
    if (timeout >= 0)
    {
      if (select(timeout) == 0) break; // timeout
    }
    c = readChar();
    if (c < 0) return c;
    buf[i] = (unsigned char) c;
    retlen = i+1;
  }
  if (retlen <= 0) return -1;
  return retlen;*/
	
	int  ret;
	if (m_fdSerial == -1) 
	{
		return -1;
	}

	if (!pBuf) 
	{
		return -2;
	}

	if (iLen < 0)
	{
		return -3;
	}

	if (iTimeout >= 0)
	{
		if (select(iTimeout) == 0)  // timeout
		{
			return -4;
		}
	}
	
	ret = read(m_fdSerial, pBuf, iLen);
	if (ret == -1)
	{
		return -5;
	}

	return ret;
#endif

#ifdef XNWIN
  unsigned long retlen;
  if (m_hdlSerial == INVALID_HANDLE_VALUE) 
  {
	  return -1;
  }

  if (!pBuf) 
  {
	  return -2;
  }

  if (iLen < 0)
  {
	  return -3;
  }

  if (iTimeout >= 0) 
  {
	  select(iTimeout);
  }

  ReadFile(
                 m_hdlSerial,     // handle of file to read
                 pBuf,     // pointer to buffer that receives data
                 iLen,     // number of bytes to read
                 &retlen, // pointer to number of bytes read
                 NULL     // pointer to structure for data
           );
  if (retlen >= 0)
  {
     return (int) retlen;
  }

  return -4;
#endif
}

// Linux
// >0 - write的字节数
// -1 - Serial is not opened
// -2 - Write FAILED

// Windows
// >0 - write的字节数
// -1 - Serial is not opened
// -2 - Write FAILED
int CXnSerial::writeChar(unsigned char uchar)
{
#ifdef XNLINUX
	int ret;
	if (m_fdSerial == -1) 
	{
		return -1;
	}
	ret = write(m_fdSerial, &uchar, 1);
	if (ret < 0)
	{
		return -2;
	}
	//tcflush(fd, TCIOFLUSH);
	return ret;
#endif

#ifdef XNWIN
	BOOL ret;
	unsigned long len;

	if (m_hdlSerial == INVALID_HANDLE_VALUE) 
	{
		return -1;
	}

	ret = WriteFile(
		m_hdlSerial,    // handle to file to write to
		&uchar, // pointer to data to write to file
		1,      // number of bytes to write
		&len,   // pointer to number of bytes written
		NULL    // pointer to structure for overlapped I/O
		);

	if (ret) 
	{
		return (int) len;
	}
	return -2;
#endif
}

// Linux
// >0 - write的字节数
// -1 - Serial is not opened
// -2 - Write FAILED

// Windows
// >0 - write的字节数
// -1 - Serial is not opened
// -2 - Write FAILED
int CXnSerial::writeBlock(const unsigned char *pBuf, int iLen)
{
#ifdef XNLINUX
	int ret;

	if (m_fdSerial == -1) 
	{
		return -1;
	}
	
	ret = write(m_fdSerial, pBuf, iLen);
	//tcflush(fd, TCIOFLUSH);

	if (ret < 0)
	{
		ret = -2;
	}
	
	return ret;
#endif


#ifdef XNWIN
	BOOL ret;
	unsigned long retlen;

	if (m_hdlSerial == INVALID_HANDLE_VALUE) 
	{
		return -1;
	}

	retlen = iLen;
	ret = WriteFile(
		m_hdlSerial,     // handle to file to write to
		pBuf,     // pointer to data to write to file
		iLen,     // number of bytes to write
		&retlen, // pointer to number of bytes written
		NULL     // pointer to structure for overlapped I/O
		);

	if (ret) 
	{
		return (int) retlen;
	}

	return -2;
#endif
}

// >0 - read到的字节数
// -1 - pBuf is NULL
// -2 - iMaxLen <= 1
// -3 - timeout
// -4 - readChar FAILED
int CXnSerial::readLine(unsigned char *pBuf, int iMaxLen, int iTimeout)
{
	int i,c,ret;

	if (!pBuf)
	{
		return -1;
	}
	
	if (iMaxLen <= 1) 
	{
		return -2;
	}

	ret = 0;
	pBuf[iMaxLen - 1] = '\0';

	for (i = 0; i < iMaxLen - 2; ++i)
	{
		ret = i;
		if (iTimeout > 0)
		{
			if (0 == select(iTimeout)) 
			{
				return -3;
			}
		}
		c = readChar();
		if (c < 0)
		{
			pBuf[i] = '\0';
			ret = -4;
			break;
		}

		pBuf[i] = (unsigned char) c;
		ret = i + 1;  // 添加字符成功
		if (c < ' ' && c != '\t')
		{
			pBuf[i + 1] = '\0';
			break;
		}
	}

	return ret;
}
